/// Copyright 2007-8 Faraday Media
/// 
/// Licensed under the Apache License, Version 2.0 (the "License"); 
/// you may not use this file except in compliance with the License. 
/// You may obtain a copy of the License at 
/// 
///   http://www.apache.org/licenses/LICENSE-2.0 
/// 
/// Unless required by applicable law or agreed to in writing, software 
/// distributed under the License is distributed on an "AS IS" BASIS, 
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
/// See the License for the specific language governing permissions and 
/// limitations under the License.
using System;
using System.CodeDom;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

namespace MetaSharp.XML.Strategies {
  /// <summary>
  /// Strategy for handling transforming a list of elements into a dictionary.
  /// </summary>
  public class SequenceDictionaryPropertyStrategy : SequencePropertyStrategy {
    #region IPropertyStrategy Members
    /// <summary>
    /// Checks whether the given strategy applies to a property, based on whether that property is a dictionary.
    /// </summary>
    /// <param name="pProp">the property to check</param>
    /// <returns>true - strategies apply</returns>
    public override bool AppliesToProperty(PropertyInfo pProp) {
      return base.AppliesToProperty(pProp) && pProp.PropertyType.IsGenericType && 
        typeof(IReadOnlyDictionary<,>).IsAssignableFrom(pProp.PropertyType.GetGenericTypeDefinition()) &&
        !typeof(IEnumerable).IsAssignableFrom(pProp.PropertyType.GetGenericArguments()[1]);
    }
    #endregion

    #region SequencePropertyStrategy Members
    /// <summary>
    /// Generates a statement that creates a dictionary of the correct type.
    /// </summary>
    /// <param name="pContext">the context of the generation</param>
    /// <param name="pProp">the property being generated</param>
    /// <returns>the initialisation expression</returns>
    protected override CodeExpression GetInitResultExpression(GenerationContext pContext, PropertyInfo pProp) {
      return new CodeObjectCreateExpression(GetResultType(pProp));
    }

    /// <summary>
    /// Returns the type that the internal result storage should use.
    /// </summary>
    /// <param name="pProp">the property being generated</param>
    /// <returns>the type to use</returns>
    protected override Type GetResultType(PropertyInfo pProp) {
      return typeof (Dictionary<,>).MakeGenericType(typeof (string), GetElementType(pProp));
    }

    /// <summary>
    /// Retrieves the type of the element in the given property by looking at the generic arguments of the dictionary.
    /// </summary>
    /// <param name="pProp">the property being generated</param>
    /// <returns>the element type</returns>
    protected override Type GetElementType(PropertyInfo pProp) {
      return pProp.PropertyType.GetGenericArguments()[1];
    }

    /// <summary>
    /// Generates the code to store an element into the dictionary.
    /// </summary>
    /// <param name="pContext">the context of the generation</param>
    /// <param name="pProp">the property being generated</param>
    /// <param name="pResult">the result object</param>
    /// <param name="pPosition">the expression that retrieves the current position in the element array</param>
    /// <param name="pConverted">the expression that represents the converted value</param>
    /// <returns>the generated statement</returns>
    protected override CodeStatement[] GetConvertedResultStoreStatements(GenerationContext pContext, PropertyInfo pProp,
                                                                         CodeExpression pResult, CodeExpression pPosition, CodeExpression pConverted) {
      AutoWrapperKeyAttribute keyAttr = AttributeHelper.GetAttribute<AutoWrapperKeyAttribute>(pProp);

      return new CodeStatement[] {
        new CodeVariableDeclarationStatement(GetElementType(pProp), "resultObj", pConverted),
        new CodeExpressionStatement(
          new CodeMethodInvokeExpression(
            pResult, "Add", 
            new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("resultObj"), keyAttr.KeyAttribute),
            new CodeVariableReferenceExpression("resultObj")))
      };
    }

    /// <summary>
    /// Generates the return statement for the dictionary.
    /// </summary>
    /// <param name="pContext">the context of the generation</param>
    /// <param name="pProp">the property being generated</param>
    /// <param name="pResult">the result object</param>
    /// <returns>the statement for the return value</returns>
    protected override CodeMethodReturnStatement GetReturnStatement(GenerationContext pContext, PropertyInfo pProp, CodeExpression pResult) {
      return new CodeMethodReturnStatement(
        new CodeObjectCreateExpression(
          typeof (ReadOnlyDictionary<,>).MakeGenericType(typeof (string), GetElementType(pProp)),
          pResult));
    }

    /// <summary>
    /// Retrieves the key for the dictionary so it will be added as a mandatory add parameter.
    /// </summary>
    /// <param name="pContext">the generation context</param>
    /// <param name="pProp">the property being generated</param>
    /// <returns>the list of parameters that need to be declared</returns>
    protected override CodeParameterDeclarationExpression[] ProvideMandatoryAddParameters(GenerationContext pContext, PropertyInfo pProp) {
      AutoWrapperKeyAttribute keyAttr = AttributeHelper.GetAttribute<AutoWrapperKeyAttribute>(pProp);

      return new CodeParameterDeclarationExpression[] {
        new CodeParameterDeclarationExpression(typeof(string), "p" + keyAttr.KeyAttribute)
                                                      };
    }

    /// <summary>
    /// Sets the key for the dictionary so it will be added as a mandatory add parameter.
    /// </summary>
    /// <param name="pContext">the generation context</param>
    /// <param name="pProp">the property being generated</param>
    /// <param name="pResultExpr">the expression that can be used to access the result variable</param>
    /// <returns>the code statements to apply the mandatory parameters</returns>
    protected override CodeStatement[] ApplyMandatoryAddParameters(GenerationContext pContext, PropertyInfo pProp, CodeExpression pResultExpr) {
      AutoWrapperKeyAttribute keyAttr = AttributeHelper.GetAttribute<AutoWrapperKeyAttribute>(pProp);

      return new CodeStatement[] {
        new CodeAssignStatement(new CodePropertyReferenceExpression(pResultExpr, keyAttr.KeyAttribute), new CodeVariableReferenceExpression("p" + keyAttr.KeyAttribute))
                                 };
    }


    /// <summary>
    /// Returns the statements necessary to remove an item from the dictionary cache.
    /// </summary>
    /// <param name="pContext">the generation context</param>
    /// <param name="pProp">the property being generated</param>
    /// <param name="pItemExpr">an expression to access the item being removed</param>
    protected override CodeStatement[] GetCacheRemoveStatements(GenerationContext pContext, PropertyInfo pProp,
                                                                CodeExpression pItemExpr) {
      CodeExpression cacheRef = MethodHelper.GenerateCacheExpression(pProp);
      AutoWrapperKeyAttribute keyAttr = AttributeHelper.GetAttribute<AutoWrapperKeyAttribute>(pProp);

      return new CodeStatement[] {
        new CodeExpressionStatement(new CodeMethodInvokeExpression(cacheRef, "Remove", new CodePropertyReferenceExpression(pItemExpr, keyAttr.KeyAttribute)))
      };
    }

    /// <summary>
    /// Generates code to walk through the elements of the dictionary.
    /// </summary>
    /// <param name="pContext">the generation context</param>
    /// <param name="pProp">the property being generated</param>
    /// <param name="pHandleItemDelegate">the delegate to be called to check and action the removal</param>
    /// <returns></returns>
    protected override CodeStatement[] GenerateEnumerateForWalk(GenerationContext pContext, PropertyInfo pProp, GenerateHandleItemDelegate pHandleItemDelegate) {
      CodeExpression cacheRef = MethodHelper.GenerateCacheExpression(pProp);

      CodeVariableDeclarationStatement valueColDecl =
        new CodeVariableDeclarationStatement(typeof (ICollection<>).MakeGenericType(GetElementType(pProp)), "valuesCol",
                                             new CodePropertyReferenceExpression(cacheRef, "Values"));
      CodeVariableReferenceExpression valuesColRef = new CodeVariableReferenceExpression("valuesCol");
      CodeVariableDeclarationStatement valueArrDecl = new CodeVariableDeclarationStatement(GetElementType(pProp).MakeArrayType(), "values",
        new CodeArrayCreateExpression(GetElementType(pProp).MakeArrayType(), new CodePropertyReferenceExpression(valuesColRef, "Count")));
      CodeVariableReferenceExpression valueArrRefExpr = new CodeVariableReferenceExpression("values");
      CodeMethodInvokeExpression copyToArrayExpr =
        new CodeMethodInvokeExpression(valuesColRef, "CopyTo", valueArrRefExpr, new CodePrimitiveExpression(0));

      CodeVariableReferenceExpression indexerExpr = new CodeVariableReferenceExpression("i");
      
      CodeIterationStatement iterate = new CodeIterationStatement(
        new CodeVariableDeclarationStatement(typeof(int), "i", new CodePrimitiveExpression(0)),
        new CodeBinaryOperatorExpression(indexerExpr, CodeBinaryOperatorType.LessThan, new CodePropertyReferenceExpression(valueArrRefExpr, "Length")),
        new CodeAssignStatement(indexerExpr, new CodeBinaryOperatorExpression(indexerExpr, CodeBinaryOperatorType.Add, new CodePrimitiveExpression(1))));
      CodeExpression valueRef = new CodeIndexerExpression(valueArrRefExpr, indexerExpr);

      AutoWrapperKeyAttribute keyAttr = AttributeHelper.GetAttribute<AutoWrapperKeyAttribute>(pProp);

      iterate.Statements.AddRange(pHandleItemDelegate(
        valueRef,
        new CodeStatement[] {
          // No need to change indexer, since we took a static snapshot

          new CodeExpressionStatement(new CodeMethodInvokeExpression(cacheRef, "Remove", new CodePropertyReferenceExpression(valueRef, keyAttr.KeyAttribute))),
        }));

      return new CodeStatement[] { valueColDecl, valueArrDecl, new CodeExpressionStatement(copyToArrayExpr), iterate };
    }
    #endregion
  }
}
